//
//  TCTreeListView.swift
//  TCTreeListView
//
//  Created by 叮叮 on 2021/4/26.
//

import UIKit

class TCTreeListView: UIView, UITableViewDelegate, UITableViewDataSource {

    var data: Dictionary<String, Any?>? {
        willSet {
            if let value = newValue {
                array = Node.construct(dic: value)
                tableView?.reloadData()
            }
        }
    }
    private var array: Array<Node> = []
    private(set) var tableView: UITableView?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
                
        tableView = UITableView(frame: bounds, style: .grouped)
        tableView?.delegate = self
        tableView?.dataSource = self
        tableView?.register(UITableViewCell.self, forCellReuseIdentifier: "TreeCell")
        addSubview(tableView!)
    }
    
    func insertNodes(nodes: Array<Node>, indexPath: IndexPath) -> Array<IndexPath> {
        array.insert(contentsOf: nodes, at: indexPath.row + 1)
        var indexPaths: Array<IndexPath> = []
        for index in 0..<nodes.count {
            let newIndexPath = IndexPath(row: indexPath.row + index + 1, section: indexPath.section)
            indexPaths.append(newIndexPath)
        }
        return indexPaths
    }
    
    /// 删除应该删除的节点
    func deleteNodes(node parent: Node, indexPath: IndexPath) -> Array<IndexPath> {
        let indexPaths = indexPathOfNodes(node: parent, indexPath: indexPath)
        /// 通过删除节点的下标，找到对应的节点并存储在数组中
        var deleteNodesArray = Array<Node>()
        indexPaths.forEach { (IndexPath) in
            let node = array[IndexPath.row]
            deleteNodesArray.append(node)
        }
        
        /// 从数据源中删除存储的节点数组
        deleteNodesArray.forEach { (node) in
            array.removeAll { (sub) -> Bool in
                return sub === node
            }
        }
        
        return indexPaths
    }
    
    /// 获取要被删除的节点的下标
    func indexPathOfNodes(node parent: Node, indexPath: IndexPath) -> Array<IndexPath> {
        guard let nodes = parent.nodes else {
            return []
        }
        
        var indexPaths: Array<IndexPath> = []
        
        nodes.forEach { (node) in
            /// 获取到传入节点的子节点的下标并构建为 IndexPath
            if let index = array.firstIndex(where: { (sub) -> Bool in
                return sub === node
            }) {
                let indexPath = IndexPath(row: index, section: indexPath.section)
                indexPaths.append(indexPath)
            }
            
            /// 如果传入节点的某个子节点是展开的，那么再递归该节点的子节点，保证点击的节点下的节点树中的节点都被找到，并且将展开的节点折叠起来
            if !node.fold {
                node.fold = true
                let indexArray = indexPathOfNodes(node: node, indexPath: indexPath)
                indexPaths.append(contentsOf: indexArray)
            }
        }
        
        return indexPaths
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return array.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TreeCell", for: indexPath)
        
        let node = array[indexPath.row]
        
        /// 为了展示效果，标题前面拼接空白占位文本
        cell.textLabel?.text = node.spaces + node.title
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        let node = array[indexPath.row]
        
        guard let nodes = node.nodes else { return }
        
        /// 如果节点是折叠的，那么需要展开该节点
        /// 如果节点是展开的，那么需要折叠该节点
        if node.fold {
            let indexArray = insertNodes(nodes: nodes, indexPath: indexPath)
            tableView.insertRows(at: indexArray, with: .middle)
        } else {
            let indexArray = deleteNodes(node: node, indexPath: indexPath)
            tableView.deleteRows(at: indexArray, with: .middle)
        }
        node.fold = !node.fold
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

class Node {
    /// 子节点
    var nodes: Array<Node>?
    /// -1表示无效层级
    var level: Int = -1
    /// 标题
    var title: String = ""
    /// 空白占位
    var spaces: String = ""
    /// 父节点
    var parent: Node?
    /// 是否折叠
    var fold = true
    
    
    static func construct(dic: Dictionary<String, Any?>) -> Array<Node> {
        return construct(dic: dic, parent: nil)
    }
    
    private static func construct(dic: Dictionary<String, Any?>, parent: Node?) -> Array<Node> {
        
        /// 应为 keys 是无序的，所以防止每次展示的数据顺序可能发生变化，故将 keys 进行一个排序
        let keys = dic.keys.sorted()
        
        var nodes = [Node]()
        
        for key in keys {
            let parentLevel = parent?.level ?? -1
            
            let node = Node()
            node.title = key
            node.parent = parent
            node.level = parentLevel + 1
            
            /// 通过节点的层级来确定空白占位的长度
            /// 为了增加显示效果
            var spaces = ""
            for _ in 0..<node.level {
                spaces.append("   ")
            }
            node.spaces = spaces
            
            /// 结果可能有三种类型
            /// 1、字符串 2、字符串数组 3、字典数组
            /// 如果是字符串或者字符串数组，都要将其转换成字典数组，能保证递归调用正确性
            if let value = dic[key] {
                if value is Array<String> {
                    var stringToDic: Dictionary<String, Any?> = [:]
                    let array = value as! Array<String>
                    array.forEach { (text) in
                        stringToDic.updateValue(nil, forKey: text)
                    }
                    node.nodes = construct(dic: stringToDic, parent: node)
                } else if value is Dictionary<String, Any?> {
                    node.nodes = construct(dic: value as! Dictionary<String, Any?>, parent: node)
                } else if value is String {
                    var stringToDic: Dictionary<String, Any?> = [:]
                    let valueString = value as! String
                    stringToDic.updateValue(nil, forKey: valueString)
                    node.nodes = construct(dic: stringToDic, parent: node)
                }
            }
            nodes.append(node)
        }
        
        return nodes
    }
}
